﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.Threading;
using System.Net;
using System.Net.Sockets;

namespace NetworkingLibrary.Servers.TCP
{
    public interface ITCPServer : IServer
    {
        IPAddress LocalAddress
        {
            get;
        }
    }
    public class TCPServer : ITCPServer
    {
        private int maxFlowRate = 5;
        public int MaxFlowRate
        {
            get
            {
                return maxFlowRate;
            }
            set
            {
                maxFlowRate = value;
            }
        }
        DateTime lastSendTime = new DateTime();
        public DateTime LastSendTime
        {
            get
            {
                return lastSendTime;
            }
            set
            {
                lastSendTime = value;
            }
        }
        
        private int bufferSize = 65000;
        public int BufferSize
        {
            get
            {
                return bufferSize;
            }
            set
            {
                bufferSize = value;
            }
        }
        bool initialised = false;
        public bool Initialised
        {
            get
            {
                return initialised;
            }
        }
        bool terminating = false;
        public bool Terminating
        {
            get
            {
                return terminating;
            }
            set
            {
                if(value)
                {
                    terminating = value;
                }
            }
        }

        //Events have to be overriden so that Invoke can be called on them
        public virtual event Events.ClientConnectDelegate OnClientConnect = delegate { };
        public virtual event Events.ClientDisconnectDelegate OnClientDisconnect = delegate { };
        public virtual event Events.MaxConnectionsDelegate OnMaxConnections = delegate { };
        public virtual event Events.MessageReceivedDelegate OnMessageReceived = delegate { };
        public virtual event Events.ErrorDelegate OnError = delegate { };
        
        private Dictionary<Guid, Clients.TCP.TCPClient> allClients = new Dictionary<Guid,Clients.TCP.TCPClient>();
        public Dictionary<Guid, Clients.IClient> AllClients
        {
            get 
            {
                return allClients.ToDictionary(x => x.Key, x => x.Value as Clients.IClient);
            }
        }

        public IPAddress LocalAddress
        {
            get
            {
                return ((IPEndPoint)TheListener.LocalEndpoint).Address;
            }
        }

        private int maxConnections = 10;
        public int MaxConnections
        {
            get
            {
                return maxConnections;
            }
            set
            {
                maxConnections = value;
            }
        }

        private TcpListener TheListener;
        private AsyncCallback AcceptSocketCallback;

        public TCPServer()
        {
        }
        public TCPServer(Events.MaxConnectionsDelegate OnMaxConnectionsReached, Events.MessageReceivedDelegate OnAMessageReceived)
        {
            OnMaxConnections += OnMaxConnectionsReached;
            OnMessageReceived += OnAMessageReceived;
        }

        public bool Initialise(int Port, string Addr)
        {
            try
            {
                TheListener = new TcpListener(IPAddress.Parse(Addr), Port);
                AcceptSocketCallback = new AsyncCallback(AcceptSocket);
                return true;
            }
            catch (Exception e)
            {
                OnError.Invoke(new Events.ErrorEventArgs(e));
            }
            return false;
        }

        public bool Start(int Port, string Addr, int? MaximumConnections = null)
        {
            try
            {
                terminating = false;

                bool OK = true;

                if (!Initialised)
                {
                    OK = initialised = Initialise(Port, Addr);
                }

                if (OK)
                {
                    MaxConnections = MaximumConnections.HasValue ? MaximumConnections.Value : MaxConnections;
                    TheListener.Start(MaxConnections);
                    AcceptingClient = true;
                    TheListener.BeginAcceptSocket(AcceptSocketCallback, null);
                }

                return OK;
            }
            catch (Exception e)
            {
                OnError.Invoke(new Events.ErrorEventArgs(e));
            }
            return false;
        }
        public bool Stop()
        {
            try
            {
                Terminating = true;

                TheListener.Stop();
                TheListener = null;

                int Fails = 0;
                Dictionary<Guid, Clients.TCP.TCPClient> DisconClients = allClients;
                for (int i = 0; i < DisconClients.Count; i++)
                {
                    try
                    {
                        Clients.TCP.TCPClient AClient = allClients.ElementAt(i).Value;
                        if (!AClient.Disconnect())
                        {
                            Fails++;
                        }
                    }
                    catch
                    {
                    }
                }

                initialised = false;
                return Fails == 0;
            }
            catch (Exception e)
            {
                OnError.Invoke(new Events.ErrorEventArgs(e));
            }
            return false;
        }

        private bool AcceptingClient = false;
        private void AcceptSocket(IAsyncResult Result)
        {
            try
            {
                if (!Terminating)
                {
                    if (allClients.Count < MaxConnections)
                    {
                        if (Result != null && TheListener != null)
                        {
                            AcceptingClient = false;
                            Socket TheSocket = TheListener.EndAcceptSocket(Result);
                            if (TheSocket != null)
                            {
                                TheSocket.Blocking = false;
                                TheSocket.LingerState = new LingerOption(true, 5);
                                TheSocket.NoDelay = true;
                                TheSocket.ReceiveBufferSize = BufferSize;
                                TheSocket.SendBufferSize = BufferSize;

                                if (TheSocket.Connected)
                                {
                                    Clients.TCP.TCPClient TheClient = CreateClient(TheSocket);
                                    TheClient.BufferSize = BufferSize;
                                    TheClient.OnMessageReceived += new Events.MessageReceivedDelegate(TheClient_OnMessageReceived);
                                    TheClient.OnDisconnect += new Events.ClientDisconnectDelegate(ClientDisconnect);
                                    allClients.Add(TheClient.ID, TheClient);
                                    if (!TheClient.Start())
                                    {
                                        TheSocket.Close();
                                        try
                                        {
                                            allClients.Remove(TheClient.ID);
                                        }
                                        catch
                                        {
                                        }
                                    }
                                    else
                                    {
                                        OnClientConnect.Invoke(new Events.ClientConnectEventArgs(TheClient, allClients.Count));
                                    }
                                }

                                AcceptingClient = true;
                                TheListener.BeginAcceptSocket(AcceptSocketCallback, null);
                            }
                        }
                    }
                    else
                    {
                        Socket TheSocket = TheListener.EndAcceptSocket(Result);
                        TheSocket.Close();
                    }

                    if(allClients.Count >= maxConnections)
                    {
                        if (OnMaxConnections != null)
                        {
                            OnMaxConnections.Invoke(new Events.MaxConnectionsEventArgs(MaxConnections));
                        }
                    }
                }
            }
            catch (Exception e)
            {
                OnError.Invoke(new Events.ErrorEventArgs(e));
            }
        }
        private void ClientDisconnect(Events.ClientDisconnectEventArgs e)
        {
            try
            {
                allClients.Remove(e.TheClient.ID);
                try
                {
                    OnClientDisconnect.Invoke(e);

                    if (!AcceptingClient && allClients.Count < MaxConnections)
                    {
                        AcceptingClient = true;
                        TheListener.BeginAcceptSocket(AcceptSocketCallback, null);
                    }
                }
                catch
                {
                }
            }
            catch
            {
            }
        }

        private void TheClient_OnMessageReceived(Events.MessageReceivedEventArgs e)
        {
            try
            {
                if (e.TheMessage != null)
                {
                    OnMessageReceived.Invoke(e);
                }
            }
            catch(Exception ex)
            {
                OnError.Invoke(new Events.ErrorEventArgs(ex));
            }
        }
        
        public bool Send(byte[] TheBuffer)
        {
            try
            {
                return Send(new Messages.DataMessage(TheBuffer));
            }
            catch (Exception ex)
            {
                OnError.Invoke(new Events.ErrorEventArgs(ex));
            }
            return false;
        }
        public bool Send(byte[] TheBuffer, Guid ExcludeID)
        {
            try
            {
                return Send(new Messages.DataMessage(TheBuffer), ExcludeID);
            }
            catch (Exception ex)
            {
                OnError.Invoke(new Events.ErrorEventArgs(ex));
            }
            return false;
        }
        public bool SendTo(byte[] TheBuffer, Guid ToID)
        {
            try
            {
                return SendTo(new Messages.DataMessage(TheBuffer), ToID);
            }
            catch (Exception ex)
            {
                OnError.Invoke(new Events.ErrorEventArgs(ex));
            }
            return false;
        }
        public bool Send(Messages.MessageObject TheMessage)
        {
            try
            {
                return DoSend(TheMessage, allClients.Values.ToList());
            }
            catch (Exception ex)
            {
                OnError.Invoke(new Events.ErrorEventArgs(ex));
            }
            return false;
        }
        public bool Send(Messages.MessageObject TheMessage, Guid ExcludeID)
        {
            try
            {
                return DoSend(TheMessage, (from PotClients in allClients
                                           where (PotClients.Key != ExcludeID)
                                           select PotClients.Value).ToList());
            }
            catch (Exception ex)
            {
                OnError.Invoke(new Events.ErrorEventArgs(ex));
            }
            return false;
        }
        public bool SendTo(Messages.MessageObject TheMessage, Guid ToId)
        {
            try
            {
                if (allClients.ContainsKey(ToId))
                {
                    return DoSend(TheMessage, new Clients.TCP.TCPClient[] { allClients[ToId] }.ToList());
                }
            }
            catch (Exception ex)
            {
                OnError.Invoke(new Events.ErrorEventArgs(ex));
            }
            return false;
        }
        private bool DoSend(Messages.MessageObject TheMessage, List<Clients.TCP.TCPClient> TheClients)
        {
            try
            {
                bool OK = true;

                TheClients.ForEach(delegate(Clients.TCP.TCPClient TheClient)
                {
                    OK = TheClient.Send(TheMessage) || OK;
                });

                return OK;
            }
            catch (Exception ex)
            {
                OnError.Invoke(new Events.ErrorEventArgs(ex));
            }
            return false;
        }

        public void DisconnectExcessClients()
        {
            try
            {
                while (allClients.Count > MaxConnections)
                {
                    Guid TheAddress = allClients.Last().Key;
                    Clients.TCP.TCPClient TheClient = allClients[TheAddress];
                    if (TheClient != null)
                    {
                        TheClient.Disconnect();
                        allClients.Remove(TheAddress);
                    }
                }
            }
            catch (Exception ex)
            {
                OnError.Invoke(new Events.ErrorEventArgs(ex));
            }
        }

        protected virtual Clients.TCP.TCPClient CreateClient(Socket TheSocket)
        {
            return new Clients.TCP.TCPClient(TheSocket);
        }
    }
}
